/****************************************************
*------------------GLOBAL PROPERTIES----------------*
****************************************************/

//The total pages display
totalPages = 1;
//The total number of entries on the back end
totalResults = 0;
//The starting record number for the current page
startRecordNumber = 0;
//The ending record number for the current page
endRecordNumber = 0;
//The current page number
pageNumber = 1;
//Configures the records per page to use when getting new entries.
recordsPerPage = 25;
//The most recently used NPA filter
filterNPA = "";
//The most recently used State filter
filterState = "";
//The sliding cache expiration timer in seconds
slidingExpirationSeconds = 30;
//The absolute cache expiration timer in seconds.
absoluteExpirationSeconds = null;
//Flag for whether the application is loading
loading = false;

/****************************************************
*------------------AJAX OPERATIONS------------------*
****************************************************/

/*Calls the GetAllowedList() page method
*/
function getAllowedList(argPageNumber, argRecordsPerPage, argNPAFilter, argStateFilter) {
    if(!browserSupported()) {
        return false;
    }

    //Set the page number to 1 if none is given or it is invalid
    if (!argPageNumber || isNaN(argPageNumber)) {
        argPageNumber = 1;
    }

    //Store the page number
    pageNumber = argPageNumber;

    //Store the value in filterNPA so that we remember the last value used
    filterNPA = parseInt(argNPAFilter);
    if (isNaN(filterNPA)) {
        filterNPA = "";
    }

    //Store the stateFilter value in filterState so that we remember the last value used
    filterState = argStateFilter;

    //Begin the loading state
    toggleLoadingIndicator(true);
    
    //Try to get a response from the cache for these parameters
    var cacheName = GetAllowedListCacheName(pageNumber, argRecordsPerPage, filterNPA, filterState);
    
    var cacheEntry = $.jCacher.get(cacheName);
    //Cache hit
    if (cacheEntry) {
        //Handle the response
        getAllowedEntriesHandleResponse(cacheEntry.value);
    }
    //Cache miss
    else {
        //Call GetAllowedList() on the web server
        callPageMethod("GetAllowedList", onGetAllowedEntriesSuccess, onFailure,
        "pageNumber", pageNumber,
        "recordsPerPage", argRecordsPerPage,
        "NPAFilter", filterNPA,
        "stateFilter", filterState);
    }
}

/*Calls the AllowEntry() page method
*/
function allowEntry(argNPA, argNXX, argXXXX) {
    if(!browserSupported() || !validateRanges(argNPA, argNXX, argXXXX)) {
        return false;
    }
    
    //If this is an exchange, the user must confirm first before allowing the exchange
    if (argXXXX != "" || confirm("Allowing an entire exchange will remove all related allowed ANIs from the list. Do you wish to continue?")) {
        toggleLoadingIndicator(true);

        //Call the page method
        callPageMethod("AllowEntry", onAllowEntrySuccess, onFailure,
        "npa", argNPA,
        "nxx", argNXX,
        "xxxx", argXXXX);
    }
}

/*Checks number ranges for NPA, NXX, XXXX
*/
function validateRanges(argNPA, argNXX, argXXXX) {

    //Validate that NPA is a number
    var npa = parseInt(argNPA);
    if (isNaN(npa)) {
        updateStatusBar("NPA must be a number.");
        return false;
    }

    //Validate that NXX is a number
    var nxx = parseInt(argNXX);
    if (isNaN(nxx)) {
        updateStatusBar("NXX must be a number.");
        return false;
    }

    //Initialize XXXX to nothing
    var xxxx = ""
    //If a value has been provided
    if (argXXXX.length > 0) {
        //Then validate that it is a number
        xxxx = parseInt(argXXXX);
        if (isNaN(xxxx)) {
            updateStatusBar("XXXX must be a number if it is provided.");
            return false;
        }
    }

    if (npa < 201 || npa > 999) {
        updateStatusBar("NPA must be from 201 to 999.");
        return false;
    }
    if (nxx < 200 || nxx > 999) {
        updateStatusBar("NXX must be from 200 to 999.");
        return false;
    }
    if (xxxx != "" && (xxxx < 0 || xxxx > 9999)) {
        updateStatusBar("XXXX must be from 0 to 9999.");
        return false;
    }
    return true;
}

/*Calls the DisallowEntry() page method
*/
function disallowEntry(npa, nxx, xxxx) {
    if(!browserSupported() || !validateRanges(npa, nxx, xxxx)) {
        return false;
    }
    if (!loading) {
        callPageMethod("DisallowEntry", onDisallowEntrySuccess, onFailure,
        "npa", npa,
        "nxx", nxx,
        "xxxx", xxxx);
    }
}

/*Returns a flag indicating whether the module supports the user's browser.
*/
function browserSupported() {
    if(!jQuery.support.ajax) {
        updateStatusBar("Your browser does not support AJAX, which is required by this page.");
        return false;
    }
    else {
        return true;
    }
}

/****************************************************
*----------------CONVENIENCE METHODS----------------*
****************************************************/

/*Gets a cache name for a GetAllowedList() response with the given parameters
*/
function GetAllowedListCacheName(argPageNumber, argRecordsPerPage, argFilterNPA, argFilterState) {
    return "GAL" + argPageNumber + ";" + argRecordsPerPage + ";" + argFilterNPA + ";" + argFilterState;
}

/*Turns the loading indicator on or off
*/
function toggleLoadingIndicator(argLoading) {
    //Set the global loading flag
    loading = argLoading;
    if (argLoading) {
        $loadingIndicator.css('display', 'inline');
    }
    else {
        $loadingIndicator.css('display', 'none');
    }
}

/*Update the selection for all of the entry rows
*/
function getEntryRows() {
    //Entries table body
    $allowedEntriesTableBody = $("#AllowedEntriesTableBody");
    $entryRows = $allowedEntriesTableBody.find("tr");
    return $entryRows;
}

/*Updates the status indicator
*/
function updateStatusBar(message) {
    $statusBar.html(message);
    //Also log status messages to the console
    console.log(message);
}

/*Updates the entry list with a new page
*/
function updateEntryList(allowedEntries) {
    //Generate new entries using the template
    var $newPage = $entryTemplate.tmpl(allowedEntries);

    //Just use an empty row if there are no results in the new page
    if ($newPage.length <= 0) {
        $newPage = $('<tr><td colspan="6"></td></tr>');
    }

    //Replace the entry rows with the new page
    $entryRows.replaceWith($newPage);

    //Update the selection for $entryRows
    getEntryRows();
}

/*Updates all the pagination controls
*/
function updatePageInfo(appResponse) {
    //Calculate new values for the pagination properties
    totalResults = appResponse.TotalResults;
    if (totalResults > 0) {
        pageNumber = appResponse.PageNumber;
        totalPages = Math.ceil(totalResults / recordsPerPage);
        startRecordNumber = ((pageNumber - 1) * recordsPerPage) + 1;
        endRecordNumber = startRecordNumber + $entryRows.length - 1;

        //Update page controls
        $totalPageInfo.html("Page " + pageNumber + " of " + totalPages);
        $totalRecordInfo.html(startRecordNumber + " to " + endRecordNumber + " of " + totalResults);
    }
    else {
        pageNumber = 1;
        totalPages = 1;
        startRecordNumber = 0;
        endRecordNumber = 0;

        $totalPageInfo.empty();
        $totalRecordInfo.empty();
    }
}

/*Convenience method for calling any ASP.NET page method.
*/
function callPageMethod(methodName, onSuccess, onFail) {
    var args = '';
    var l = arguments.length;
    //If methods for the page method were given
    if (l > 3) {
        //Loop through the page methods arguments
        for (var i = 3; i < l - 1; i += 2) {
            //Add a comma between args
            if (args.length != 0) {
                args += ',';
            }
            //Add the name and value of the page method argument
            args += '"' + arguments[i] + '":"' + arguments[i + 1] + '"';
        }
    }
    //Get the current URL
    var loc = window.location.href;
    //If the last character in the URL is a / then append admin_module.aspx
    loc = (loc.substr(loc.length - 1, 1) == "/") ? loc + "Default.aspx" : loc;

    //Make the AJAX call against the page method
    $.ajax({
        type: "POST",
        url: loc + "/" + methodName,
        data: "{" + args + "}",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: onSuccess,
        fail: onFail
    });
}

/*Initialize global references to "constant" elements on the page
*/
function initializeSelections() {
    //Templates
    $entryTemplate = $("#EntryTemplate");

    //Top Bars
    $statusBar = $("#StatusBar");
    $loadingIndicator = $("#LoadingIndicator");

    //Add Entry fields
    $addNPA = $("#AddNPA");
    $addNXX = $("#AddNXX");
    $addXXXX = $("#AddXXXX");
    $addEntryButton = $("#AddEntryButton");

    //Filter Fields
    $filterNPA = $("#FilterNPA");
    $filterState = $("#FilterState");

    //Pagination Info
    $totalRecordInfo = $("#TotalRecordInfo");
    $totalPageInfo = $("#TotalPageInfo");
    $paginationFooter = $("#PaginationFooter");

    //Entries table
    $allowedEntriesTable = $("#AllowedEntriesTable");

    //The First Page button
    $firstPage = $("#FirstPage");
    //The Previous Page button
    $previousPage = $("#PreviousPage");
    //The Next Page button
    $nextPage = $("#NextPage");
    //The Last Page button
    $lastPage = $("#LastPage");

    //Initialize the list of entries
    getEntryRows();
}

/*Bind events to the page
*/
function bindEvents() {
    //Called when the page is finished loading
    $(window).load(onLoad);

    //Add an entry when the add entry button is clicked
    $addEntryButton.click(function (event) {
        //If the application is not loading
        if (!loading) {
            //Then allow the entry
            allowEntry($addNPA.val(), $addNXX.val(), $addXXXX.val());
        }
        event.preventDefault();
    });

    //Get entries when the user hits enter in the page number field or any of the filter fields
    $filterNPA.keypress(onReturnKeyPressedEntryInputs);
    $filterState.keypress(onReturnKeyPressedEntryInputs);

    //Initialize click handlers for page navigation fields
    $firstPage.click(function (event) {
        if (pageNumber != 1 && !loading) {
            getAllowedList(1, recordsPerPage, filterNPA, filterState);
        }
    });

    $previousPage.click(function (event) {
        if (startRecordNumber > 1 && !loading) {
            getAllowedList((pageNumber - 1) >= 1? pageNumber - 1 : 1, recordsPerPage, filterNPA, filterState);
        }
    });

    $nextPage.click(function (event) {
        if (endRecordNumber < totalResults && !loading) {
            getAllowedList((pageNumber + 1) <= totalPages ? pageNumber + 1 : totalPages, recordsPerPage, filterNPA, filterState);
        }
    });

    $lastPage.click(function (event) {
        if (pageNumber != totalPages && !loading) {
            getAllowedList(totalPages, recordsPerPage, filterNPA, filterState);
        }
    });

}

/*Handle the application response after it has been retrieved from the cache or gotten from the web server
*/
function getAllowedEntriesHandleResponse(appResponse)
{
    //Update the table list
    updateEntryList(appResponse.AllowedEntries);

    //Update the pagination controls
    updatePageInfo(appResponse);
    
    //Clear the loading indicator
    toggleLoadingIndicator(false);
}

/*****************************************************
*---------------------CALLBACKS---------------------*
****************************************************/

/*Called when the user presses enter in any of the page input fields
*/
function onReturnKeyPressedEntryInputs(event) {
    //If this is the return key and we are not loading
    if(((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) && !loading) {
        //Get the allowed entries
        getAllowedList(pageNumber, recordsPerPage, $filterNPA.val(), $filterState.val());
    }
}

/*Called when the document is ready to be manipulated
*/
jQuery(function ($) {
    //Initialize global references to "constant" elements on the page
    initializeSelections();

    //Initialize the loading indicator to false
    toggleLoadingIndicator(false);

    //Bind events to the page
    bindEvents();

    //Initialize DataTables plugin
    $allowedEntriesTable.dataTable(
    {
        "bPaginate": false,
        "sScrollY": "200px",
        "bScrollInfinite": true,
        "bProcessing": true
    });
});



//Called when the window loads
function onLoad() {
    //Get the initial list
    getAllowedList(1, recordsPerPage, "", "");
}

/*Callback for when GetAllowedList() returns
response: The response from the web server
*/
function onGetAllowedEntriesSuccess(response) {
    //If there is a response
    if (response) {
        //Get the application's response
        var appResponse = response.d;

        //Get the response code
        var responseCode = appResponse.ResponseCode;
        //If entries were successfully retrieved
        if (responseCode == 0) {
        
            //Cache the appResponse object with the parameters that were last used
            var cacheName = GetAllowedListCacheName(pageNumber, recordsPerPage, filterNPA, filterState);
            //Add this item to the cache with an expiration
            $.jCacher.add(cacheName, appResponse, slidingExpirationSeconds, absoluteExpirationSeconds);
            
            //Handle the application response
            getAllowedEntriesHandleResponse(appResponse);
        }
        else if (responseCode == 1) {
            handleKnownErrorCode("Invalid arguments were given.");
        }
        //An exception has been thrown and information is being passed to the client
        else if (responseCode == -1) {
            //Get the exception message and stack trace
            handleException(appResponse);
        }
        else {
            //This is an unknown error
            handleUnknownError();
        }
    }
    else {
        //This is an unknown error
        handleUnknownError();
    }
}

/*Callback for when AllowEntry() returns
    response: The response from the web server
*/
function onAllowEntrySuccess(response) {
    if (response) {
        //Get the application's response
        var appResponse = response.d;

        //Get the response code
        var responseCode = appResponse.ResponseCode;
        //If the entry was successfully added
        if (responseCode == 0) {
            //Invalidate the cache
            $.jCacher.clear();

            //Call GetAllowedList() with the current page number, records per page, and the last filters that were used
            //So, we ignore whatever is actually currently in the filter input fields.
            getAllowedList(pageNumber, recordsPerPage, filterNPA, filterState);

            //Notify the user that the entry was added
            updateStatusBar("Entry was successfully added.");
        }
        //Handle known error codes for this operation
        else if (responseCode == 1) {
            handleKnownErrorCode("Invalid arguments were given.");
        }
        else if (responseCode == 2) {
            handleKnownErrorCode("The exchange is already allowed.");
        }
        else if (responseCode == 3) {
            handleKnownErrorCode("The associated exchange does not exist.");
        }
        else if (responseCode == 4) {
            handleKnownErrorCode("The ANI is already allowed.");
        }
        //An exception has been thrown and information is being passed to the client
        else if (responseCode == -1) {
            handleException(appResponse);
        }
        else {
            //This is an unknown error
            handleUnknownError();
        }
    }
    else {
        //This is an unknown error
        handleUnknownError();
    }
}

/*Callback for when DisallowEntry() returns
    response: The response from the web server
*/
function onDisallowEntrySuccess(response) {
    if (response) {
        //Get the application's response
        var appResponse = response.d;

        //Get the response code
        var responseCode = appResponse.ResponseCode;

        //If entry was successfully removed
        if (responseCode == 0) {

            //Invalidate the cache
            $.jCacher.clear();

            //Call GetAllowedList() with the current page number, records per page, and the last filters that were used
            //So, we ignore whatever is actually currently in the filter input fields.
            getAllowedList(pageNumber, recordsPerPage, filterNPA, filterState);

            //Notify the user that the entry was used.
            updateStatusBar("Entry was successfully removed.");
        }
        //Handle known error codes
        else if (responseCode == 1) {
            handleKnownErrorCode("Invalid arguments were given.");
        }
        else if (responseCode == 2) {
            handleKnownErrorCode("Could not remove entry because entry does not exist.");
        }
        //An exception has been thrown and information is being passed to the client
        else if (responseCode == -1) {
            handleException(appResponse);
        }
        else {
            //This is an unknown error
            handleUnknownError();
        }
    }
    else {
        //This is an unknown error
        handleUnknownError();
    }
}

/*Callback for when a page method call fails
    response: The response from the web server
*/
function onFailure(response) {
    var appResponse = response.d;

    //Get the response code
    var responseCode = appResponse.ResponseCode;

    //Clear the loading indicator
    toggleLoadingIndicator(false);

    var message = "An operation failed with error code: " + responseCode;
    //Alert the user of the failure
    updateStatusBar(message);
}

/****************************************************
*------------------ERROR HANDLING-------------------*
****************************************************/
/*Notifies the user of the result and turns of loading
*/
function handleKnownErrorCode(message) {
    updateStatusBar(message);
    toggleLoadingIndicator(false);
}

/*Handles an unexpected exception
    appResponse: The application response from the web server
*/
function handleException(appResponse) {
    var exceptionMessage = appResponse.ExceptionMessage;
    var stackTrace = appResponse.StackTrace;

    //Write the exception message to the status bar
    updateStatusBar(exceptionMessage);
    console.log(stackTrace);

    //Clear the loading indicator
    toggleLoadingIndicator(false);
}

/*Handles errors where the cause is unknown
*/
function handleUnknownError() {
    //Notify the user that there was an error.
    updateStatusBar("An unknown error has occurred.");

    //Clear the loading indicator
    toggleLoadingIndicator(false);
}

